﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MachineLearning {

	/**
	 * An instance of input data.
	 * Includes normalization of signals.
	 */
	public class Instance {
		double[] _inputValues = null;
        public double[] InputValues {
            get { return _inputValues; }
        }
		public Instance() { }
		public Instance(double[] inVals) {
			_inputValues = inVals;
		}
	}


	/**
	 * An instance of input data plus correct output for training.
	 */
    public class TrainingExample : Instance {
		double[] _outputValues = null;
        public double[] OutputValues {
            get { return _outputValues; }
        }
		public TrainingExample(double[] inVals, double[] outVals) : base(inVals) {
			_outputValues = outVals;
		}
    }

    class SignalConditioner {
        ///<summary>
        /// consuming code sets up a list of the inputs that they 
        /// will be passing to the network, they specify the min and max 
        /// ranges of the datums, then each input instance has a multiplier 
        /// to linearly scale the input data to values between 0 and 1
        /// </summary>
        protected List<ScaleAndOffset> _inputConditioners = null;
        public List<ScaleAndOffset> InputConditioners {
            get { return _inputConditioners; }
        }

        //either initialize the scales and offsets internally
        //if they are specified in the training vector file
        //else initialize them manually.
        protected List<ScaleAndOffset> _outputConditioners = null;
        public List<ScaleAndOffset> OutputConditioners {
            get { return _outputConditioners; }
        }

        public SignalConditioner() {
            _inputConditioners = new List<ScaleAndOffset>();
            _outputConditioners = new List<ScaleAndOffset>();
        }

        ///<summary>
        /// consuming code passes in the input data, and the 
        /// validate input function validates it by making sure
        /// the array lengths match, and scales the input values for the ANN
        /// </summary>
        public double[] conditionInput(double[] inputVector) {
            double[] conditioned = new double[inputVector.Length];
            if (inputVector.Length == _inputConditioners.Count) {
                for (int i = 0; i < _inputConditioners.Count; i++)
                    conditioned[i] = (inputVector[i] - _inputConditioners[i].Offset)
                        * _inputConditioners[i].Multiplier;
            }
            return conditioned;
        }

        public double[] conditionOutput(double[] outputVector) {
            double[] conditioned = new double[outputVector.Length];
            if (outputVector.Length == _outputConditioners.Count) {
                for (int i = 0; i < _outputConditioners.Count; i++)
                    conditioned[i] = (outputVector[i] - _outputConditioners[i].Offset)
                        * _outputConditioners[i].Multiplier;
                        
            }
            return conditioned;
        }

        public double[] reclaim(double[] outputVector) {
            double[] validated = new double[outputVector.Length];
            if (outputVector.Length == _outputConditioners.Count) {
                for (int i = 0; i < _outputConditioners.Count; i++)
                    validated[i] = outputVector[i] 
                        / _outputConditioners[i].Multiplier
                        + _outputConditioners[i].Offset;
            }
            return validated;
        }
    }

    public class ScaleAndOffset {
        //multiplier used to scale the input values to the network
        double _multiplier = 0.0;
        //multiplier accessor
        public double Multiplier {
            get { return _multiplier; }
        }

        //do scale and offset, offset = min
        //subtract the offset from the value when putting it
        //into the network
        //add the offset back to the output 
        //when getting values out of the network
        double _offset = 0.0;
        //multiplier accessor
        public double Offset {
            get { return _offset; }
        }
        ///<summary>
        /// construct an input instance with a min and max value
        /// i.e. the range of input values possible for the parameter
        /// </summary>
        public ScaleAndOffset(double min, double max) {
            _multiplier = 1.0 / (max - min);
            _offset = min;
        }
    }

	/**
	 * Provide I/O for sets of training examples.
	 * Perhaps also a good place to encapsulate folding methods in the future??
	 */
	public abstract class InstanceReader {
		/**
		 * Loads a set of training examples from a tab-delimited file.
		 * @param filename tab-delimited file containing double-valued training examples
		 * @param inputCount number of input values expected per training example
		 * @param outputCount number of output values expected per training example
		 * @throws InvalidDataException if the number of columns of data does not equal inputCount+outputCount
		 */
        public static Instance[] load(string filename, int inputCount) {
			List<Instance> instances = new List<Instance>();
			StreamReader reader = new StreamReader(filename);
			try {
				do {
					double[] value = parseLine(reader.ReadLine());
					if (value.Length != (inputCount)) {
						throw new InvalidDataException("Expected " 
                            + (inputCount) + " values, found " 
                            + value.Length + ".");
					}
					// create anew to avoid assumptions about deep copying in TrainingExample constructor
					double[] input = new double[inputCount];
					for (int i = 0; i < inputCount; ++i) {
						input[i] = value[i];
					}
					instances.Add(new Instance(input));
				} while (reader.Peek() != -1);
			} catch (InvalidDataException ide) {
				// re-throw - put handling in the hands of the caller
				throw ide;
			} catch {
				// file is empty - no action necessary
			} finally {
				reader.Close();
			}
			return instances.ToArray();
		}
		/**
		 * Parse a single tab-delimited line into an array of double values.
		 * @param line a tab-delimited record
		 * @throws InvalidDataException if unable to convert a field to a double.
		 */
		protected static double[] parseLine(string line) {
			string[] field = line.Split('\t');
			double[] value = new double[field.Length];
            for (int i = 0; i < field.Length; ++i) {
                if (!Double.TryParse(field[i], out value[i])) {
                    throw new InvalidDataException("Failed to convert '"
                        + field[i] + "' to a double-precision floating-point value.");
                }
            }
			return value;
		}
	}

    public abstract class ExampleReader : InstanceReader {
        /**
         * Loads a set of training examples from a tab-delimited file.
         * @param filename tab-delimited file containing double-valued training examples
         * @param inputCount number of input values expected per training example
         * @param outputCount number of output values expected per training example
         * @throws InvalidDataException if the number of columns of data does not equal inputCount+outputCount
         */
        public static TrainingExample[] load(string filename, int inputCount, int outputCount) {
			List<TrainingExample> examples = new List<TrainingExample>();
			StreamReader reader = new StreamReader(filename);
			try {
				do {
					double[] value = parseLine(reader.ReadLine());
                    if (value.Length != (inputCount + outputCount)) {
						throw new InvalidDataException("Expected "
                            + (inputCount + outputCount) + " values, found " 
                            + value.Length + ".");
					}
					// create anew to avoid assumptions about deep copying in TrainingExample constructor
					double[] input = new double[inputCount];
                    double[] output = new double[outputCount];
					for (int i = 0; i < inputCount; ++i) {
						input[i] = value[i];
					}
                    for (int i = 0; i < outputCount; ++i) {
						output[i] = value[inputCount + i];
					}
					examples.Add(new TrainingExample(input, output));
				} while (reader.Peek() != -1);
			} catch (InvalidDataException ide) {
				// re-throw - put handling in the hands of the caller
				throw ide;
			} catch {
				// file is empty - no action necessary
			} finally {
				reader.Close();
			}
			return examples.ToArray();
        }
    }

}
